#include "fs/dentry.h"
#include "init/startup.h"
#include "kernel/proc.h"
#include "kernel/slab.h"
#include "kernel/heap.h"
#include "kernel/spinlock.h"

// test for win32
#ifdef TEST_DBG_WIN32
#include <stdio.h>
#define vmalloc(p)	malloc(PAGE_SZ * p)
#endif

// global variables
kmem_cache_t*	dentry_cache = NULL;

// static variables
DECLARE_PRIVATE_SPINLOCK(dspinlock);

static listnode_t*	dentry_hashtable = NULL;
static uint			dentry_hash_pages = DENTRY_HASH_MAX_PAGES;
static uint			dentry_hash_mask = 0;
static uint			dentry_hash_shift = 0;

// dget: get a dentry and addref
static dentry_t* dget(dentry_t* d)
{
	if (NULL == d)
		return NULL;
	atomic_inc(&(d->refcnt));
	return d;
}

// dput: release a dentry by relref
static void dput(dentry_t* d)
{
	if (NULL == d) return;
	atomic_dec(&(d->refcnt));
}

static inline const uchar* killspace(const uchar* str)
{
	while (*str == ' ') str++;
	return str;
}

static inline const uchar* killslash(const uchar* str)
{
	while (*str == '/' || *str == '\\') str++;
	return str;
}

int init_path_walk(const uchar *name, dentry_t** root)
{
	task_t* task;
	if (!current || !current->task)
		return ERR_SYS_NOT_READY;

	task = current->task;
	kassert(NULL != name && NULL != root);

	if (*name == '\0')
		return ERR_BAD_PARAMETERS;
	
	if (*name == '/' || *name == '\\')
	{
		spin_lock(&dspinlock);
		*root = dget(task->fs_data.root);
		spin_unlock(&dspinlock);
		name = killslash(++name);
	}
	else
	{
		spin_lock(&dspinlock);
		*root = dget(task->fs_data.pwd);
		spin_unlock(&dspinlock);
	}
	return 0;
}

#define chr_normalize(c) \
	(((c) >= 'A' && (c) <= 'Z') ? ((c) + (uchar)('a' - 'A')) : (c))

static const uchar* path_walk_get_dentry_name(const uchar *name, qstr_t* str)
{
	uint hash;
	int scnt = 0;
	str->str = killspace(name);

	// calculate the hash of dentry name
	hash = init_name_hash();
	while (*name && *name != '/' && *name != '\\')
	{
		if (scnt >= 0)
		{
			uchar c;
			for (; scnt > 0; --scnt)
				hash = partial_name_hash(' ', hash);

			// next time check space
			scnt = -1;

			// calculate one char after spaces
			c = *name++;
			hash = partial_name_hash(chr_normalize(c), hash);
		}
		// check space
		else for (scnt = 0; *name == ' ';
			++name, ++scnt);
	}

	str->size = name - str->str;
	if (scnt >= 0) str->size -= (uint)scnt;

	str->hash = end_name_hash(hash);
	return name;
}

// handle '.' and '..'
static dentry_t* path_walk_handle_dotdot(qstr_t* str, dentry_t* curr)
{
	if (str->size > 2)
		return NULL;
	else if (str->size == 1)
	{
		// this mean '.'
		// just omit it and do nothing else
		return curr;
	}
	else if (str->str[1] == '.')
	{
		// this mean '..'
		if (curr == current->task->fs_data.root)
			return curr; // todo: need lock
		else return curr->parents;
	}
	else return NULL;
}

static listnode_t* dhash(dentry_t* parent, uint hash)
{
	hash += (unsigned long) parent / 32;
	hash = hash ^ (hash >> dentry_hash_shift) ^ (hash >> dentry_hash_shift * 2);
	return dentry_hashtable + (hash & dentry_hash_mask);
}

dentry_t* dalloc(qstr_t* name, dentry_operations_t* ops)
{
	uchar* str;
	dentry_t* ret = kmem_cache_alloc(dentry_cache);
	if (NULL == ret) return NULL;

	// set the dentry name
	if (name->size > DENTRY_SMALL_NAME_LEN - 1)
	{
		str = kmalloc(name->size + 1);
		if (NULL == str)
		{
			kmem_cache_free(ret);
			return NULL;
		}
	}
	else str = ret->small_name;

	strcpy(str, name->str);
	atomic_inc(&(ret->refcnt));
	ret->name = str;
	ret->ops = ops;
	return ret;
}

static dentry_t* cached_lookup(dentry_t* parent, qstr_t* name)
{
	listnode_t* tmp;
	listnode_t* head = dhash(parent, name->hash);
	
	spin_lock(&dspinlock);
	tmp = head->next;
	for (;;)
	{
		dentry_t* node = list_entry(dentry_t, hash, tmp);
		if (tmp == head) break;
		tmp = tmp->next;

		if (node->parents != parent)
			continue;
		if (node->ops && node->ops->compare)
		{
			if (node->ops->compare(node, name))
				continue;
		} else {
			if (strcmp(node->name, name->str))
				continue;
		}
		dget(node);
		spin_unlock(&dspinlock);
		return node;
	}
	spin_unlock(&dspinlock);
	return NULL;
}

static dentry_t* do_path_walk(dentry_t* root, const uchar* name)
{
	dentry_t* tmp = root;
	while (*name)
	{
		qstr_t dentry_name;
		name = path_walk_get_dentry_name(name, &dentry_name);
		if (!dentry_name.size) continue;

		if (dentry_name.str[0] != '.'
			|| !(tmp = path_walk_handle_dotdot(&dentry_name, root)))
		{
			// see if the file system has its own hash
			if (root->ops->hash)
				root->ops->hash(root, &dentry_name);

			// lookup the entry in the cache
			tmp = cached_lookup(root, &dentry_name);
			if (!tmp) break;	// todo
		}
		dput(root);
		root = tmp;
	}
	return tmp;
}

int path_walk(const uchar* name, dentry_t** dent)
{
	int ret;
	dentry_t *root;

	if (NULL == name || NULL == dent)
		return ERR_BAD_PARAMETERS;

	name = killspace(name);
	ret = init_path_walk(name, &root);
	if (ret) return ret;

	if (NULL == root)
		return ERR_SYS_NOT_READY;

	*dent = do_path_walk(root, name);
	return 0;
}

static void dentry_ctor(kmem_cache_t* cache, void* p, size_t sz)
{
	dentry_t* dent;
	memset(p, 0, sz);

	dent = (dentry_t*)p;
	listnode_init(dent->child);
	listnode_init(dent->hash);
	listnode_init(dent->children);
}

static void dentry_dtor(kmem_cache_t* cache, void* p, size_t sz)
{
}

static void init_dentry_hash_pages(void)
{
	uint tmp, count = 16;
	uint total_mem_mb = sys_config_data.total_memory / 1024;

	// calc the hash size
	dentry_hash_pages = 1;
	for (; total_mem_mb > count
		&& dentry_hash_pages < DENTRY_HASH_MAX_PAGES;
		count *= 2, dentry_hash_pages *= 2);

	dentry_hash_mask = tmp = dentry_hash_pages * (PAGE_SZ / sizeof(listnode_t));
	while ((tmp >>= 1UL) != 0UL) dentry_hash_shift++;

	// initialize the hash table
	dentry_hashtable = vmalloc(dentry_hash_pages);
	if (NULL == dentry_hashtable)
		bug("init_dentry_hash_pages: fail to allocate hash table\n");

	for (count = 0; count < dentry_hash_mask; ++count)
		listnode_init(dentry_hashtable[count]);

	dbg_output3("init_dentry_hash_pages: %u pages with %u entries.\n", \
		dentry_hash_pages, dentry_hash_mask);
	--dentry_hash_mask;
}

// global initialization for namei
void global_init_vfs_namei_module(void)
{
	init_dentry_hash_pages();

	// create the kmem_cache
	dentry_cache = easy_kmem_cache_create("dentry_cache",
		sizeof(dentry_t), dentry_ctor, dentry_dtor);
	if (NULL == dentry_cache)
		bug("fail to create kmem_cache: dentry_cache.\n");
}

// test for win32
#ifdef TEST_DBG_WIN32

int vfs_test(void)
{
	global_init_vfs_namei_module();
	return 0;
}

#endif
/* EOF */
